﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PerCederberg.Grammatica.Runtime;

namespace SafeScript.MetaModel
{
    /// <summary>
    /// A basic type is everything that can be instantiated, i.e. classes, interfaces, builtin-types.
    /// </summary>
    public abstract class AbstractType : Namespace
    {
        /// <summary>
        /// Used temporarily during parsing.
        /// </summary>
        private List<TypeResolver> unresolvedBaseTypes = new List<TypeResolver>();
        private Node classDef;
        /// <summary>
        /// Used to detect recursions when traversing the inheritance graph.
        /// </summary>
        private bool marker = false;
        private Dictionary<string,object> flatScope;
        private bool membersAreParsed = false;

        public Node Node { get { return this.classDef; } protected set { this.classDef = value; } }
        
        /// <summary>
        /// This flag is required, because classes nested inside a template are a template themselves
        /// although they do not feature template parameters
        /// </summary>
        // public bool IsTemplate { get; set; }
        
        public List<TemplateParameterType> TemplateParameters = new List<TemplateParameterType>();

        public List<AbstractType> BaseTypes = new List<AbstractType>();

        public Dictionary<string, object> FlatScope
        {
            get
            {
                return this.flatScope;
            }
        }

        public object GetFlatScopeElement(string ident)
        {
            object obj = null;
            this.flatScope.TryGetValue(ident, out obj);
            return obj;
        }

        public override ITypeDef Instantiate(Dictionary<TemplateParameterType, ITypeDef> templateArguments)
        {
            if (this.TemplateParameters.Count == 0)
                return this;
            return Compiler.CurrentCompiler.GetTemplateInstance(this, templateArguments);
        }

        public override bool CanBeAssignedTo(ITypeDef typedef)
        {
            if (this.IsEqualTo(typedef))
                return true;
            foreach (AbstractType iface in this.BaseTypes)
                if (iface.CanBeAssignedTo(typedef))
                    return true;
            return false;
        }

        public override int GetInheritanceDepth(ITypeDef typedef)
        {
            if (this.IsEqualTo(typedef))
                return 0;

            int best = int.MaxValue;
            foreach (AbstractType iface in this.BaseTypes)
            {
                int i = iface.GetInheritanceDepth(typedef);
                if (i == -1)
                    continue;
                best = i + 1 < best ? i + 1 : best;
            }

            if (best == int.MaxValue)
                return -1;

            return best;
        }

        public virtual AbstractType[] GetDirectBaseTypes()
        {
            AbstractType[] types = new AbstractType[this.BaseTypes.Count];
            for (int i = 0; i < types.Length; ++i)
                types[i] = this.BaseTypes[i];
            return types;
        }

        public Dictionary<string, object> ComputeFlatScope(ErrorReport report)
        {
            if (this.flatScope != null)
                return this.flatScope;
            this.flatScope = new Dictionary<string, object>();

            List<Dictionary<string, object>> scopes = new List<Dictionary<string, object>>();

            // All identifiers of this class
            foreach (string key in this.ScopeElements.Keys)
                this.flatScope[key] = this.ScopeElements[key];

            // Import the identifiers of all base types.
            foreach (AbstractType at in this.BaseTypes)
            {
                scopes.Add(at.ComputeFlatScope(report));
            }
            
            // Iterate over all base classes
            for (int i = 0; i < scopes.Count; ++i)
            {
                Dictionary<string, object> scope = scopes[i];
                // Iterate over all symbols of this base class
                foreach (string key in scope.Keys)
                {
                    // Do not inherit constructors
                    if (key == "ctor")
                        continue;
                    object obj = scope[key];

                    // Does an element of the same name exist in any of the other scopes? If so they might be in conflict
                    object other = null;
                    // A member of the same name already exists?
                    if (this.flatScope.TryGetValue(key, out other))
                    {
                        // Both members are the same? This happens if an interface is inherited multiples times
                        // and it happens with member functions of the object.
                        if (obj == other)
                            continue; // Do nothing by intention
                        if (obj is Property)
                        {
                            if (!(other is Property))
                                report.Add(new ErrorEntry(this.classDef) { Info = key, Type = ErrorType.DuplicateName });
                            else
                            {
                                Property prop1 = obj as Property;
                                Property prop2 = other as Property;
                                // If the type is equal and at least one is defined in an interface, we are safe
                                if (!prop1.Type.IsEqualTo(prop2.Type))
                                    report.Add(new ErrorEntry(this.classDef) { Info = key, Type = ErrorType.DuplicateName });
                                else
                                {
                                    if (prop2.Getter != null && prop1.Getter != null && prop1.Getter.Owner is Class && !prop2.Getter.IsOverride)
                                        report.Add(new ErrorEntry(prop2.Node) { Info = key, Type = ErrorType.OverrideMissing });
                                    if (prop2.Setter != null && prop1.Setter != null && prop1.Setter.Owner is Class && !prop2.Setter.IsOverride)
                                        report.Add(new ErrorEntry(prop2.Node) { Info = key, Type = ErrorType.OverrideMissing });
                                    // This class does not have a getter -> inherit the base getter
                                    if (prop2.Getter == null && prop1.Getter != null)
                                        prop2.Getter = prop1.Getter;
                                    // This getter overrides the getter of an interface
                                    else if (prop2.Getter != null && prop1.Getter != null && prop2.Owner == this && prop2.Owner is Class && prop1.Owner is Interface)
                                        prop2.Getter.OverridenGetter = prop1.Getter;
                                    // This class does not have a setter -> inherit the base setter
                                    if (prop2.Setter == null && prop1.Setter != null)
                                        prop2.Setter = prop1.Setter;
                                    // This setter overrides the setter of an interface
                                    else if (prop2.Setter != null && prop1.Setter != null && prop2.Owner == this && prop2.Owner is Class && prop1.Owner is Interface)
                                        prop2.Setter.OverridenSetter = prop1.Setter;
                                }
                            }
                        }
                        else if (obj is MemberVariable)
                        {
                            if (!(other is MemberVariable))
                                report.Add(new ErrorEntry(this.classDef) { Info = key, Type = ErrorType.DuplicateName });
                            else
                            {
                                MemberVariable var1 = obj as MemberVariable;
                                MemberVariable var2 = other as MemberVariable;
                                // If the type is equal and at least one is defined in an interface, we are safe
                                if (!var1.Type.IsEqualTo(var2.Type) || ( var1.Owner is Class && var2.Owner is Class))
                                    report.Add(new ErrorEntry(this.classDef) { Info = key, Type = ErrorType.DuplicateName });
                                // TODO: Compare the access modifier.
                            }
                        }
                        else if (obj is MemberFunction || obj is MemberFunctionSet)
                        {
                            if (!(other is MemberFunction || other is MemberFunctionSet))
                                report.Add(new ErrorEntry(this.classDef) { Info = key, Type = ErrorType.DuplicateName });
                            else
                            {
                                MemberFunctionSet mfs = other as MemberFunctionSet;
                                if (mfs == null)
                                {
                                    mfs = new MemberFunctionSet();
                                    mfs.Add(other as MemberFunction, report);
                                }
                                MemberFunctionSet mfs2 = obj as MemberFunctionSet;
                                if (mfs2 == null)
                                {
                                    mfs2 = new MemberFunctionSet();
                                    mfs2.Add(obj as MemberFunction, report);
                                }

                                foreach (MemberFunction func2 in mfs2.Functions)
                                {
                                    if (func2.IsStatic != mfs.Functions[0].IsStatic)
                                        report.Add(new ErrorEntry(this.classDef) { Info = key, Type = ErrorType.SameFunctionNameSameStatic });

                                    MemberFunction func = mfs.GetEquivalentFunction(func2);
                                    if (func == null)
                                        mfs.Add(func2, report);
                                    else if (func == func2)
                                    {
                                        // Do nothing by intention
                                    }
                                    else
                                    {
                                        // Does the return type match?
                                        if (!func2.ReturnType.IsEqualTo(func.ReturnType))
                                            report.Add(new ErrorEntry(this.classDef) { Info = key, Type = ErrorType.FunctionReturnTypeMismatch });
                                        // If both functions belong to different classes than one must override th other one
                                        else if (func.Owner is Class && func2.Owner is Class && !func.IsOverride)
                                            report.Add(new ErrorEntry(func.Node) { Info = key, Type = ErrorType.OverrideMissing });
                                        // If both functions belong to different interfaces -> they are in conflict
                                        else if (func2.Owner is Interface && func.Owner is Interface && func2.Owner != func.Owner)
                                            report.Add(new ErrorEntry(func.Node) { Info = key, Type = ErrorType.DuplicateNameInInterfaces });
                                        // If one function belongs to an interface and the other one belongs to this class then this is like 'override' -> update the data structure.
                                        else if (func.Owner is Class && func.Owner == this && func2.Owner is Interface)
                                            func.OverridenFunction = func2;
                                    }
                                }

                                if (mfs.Functions.Count == 1)
                                    this.flatScope[key] = mfs.Functions[0];
                                else
                                    this.flatScope[key] = mfs;
                            }
                        }
                        else if (obj is AbstractType)
                        {
                            this.flatScope[key] = obj;
                        }
                    }
                    else
                        this.flatScope[key] = obj;
                }
            }

            // Non-abstract classes must not contain abstract properties/functions in the flatscope
            // and all properties/functions of interfaces must be implemented
            if (this is Class)
            {
                bool classIsAbstract = (this as Class).IsAbstract;

                foreach (string key in this.flatScope.Keys)
                {
                    object obj = this.flatScope[key];
                    if (obj is Property)
                    {
                        Property prop = obj as Property;
                        if (prop.Getter != null && ((prop.Getter.IsAbstract && !classIsAbstract) || prop.Getter.Owner is Interface))
                            report.Add(new ErrorEntry(this.classDef) { Info = prop.Name, Type = ErrorType.PropertyGetterImplementationMissing });
                        if (prop.Setter != null && ((prop.Setter.IsAbstract && !classIsAbstract) || prop.Setter.Owner is Interface))
                            report.Add(new ErrorEntry(this.classDef) { Info = prop.Name, Type = ErrorType.PropertySetterImplementationMissing });
                    }
                    else if (obj is MemberFunction)
                    {
                        MemberFunction func = obj as MemberFunction;
                        if ( (func.IsAbstract && !classIsAbstract) || func.Owner is Interface )
                            report.Add(new ErrorEntry(this.classDef) { Info = func.Name, Type = ErrorType.FunctionImplementationMissing });
                    }
                    else if (obj is MemberFunctionSet)
                    {
                        foreach (MemberFunction func in (obj as MemberFunctionSet).Functions)
                        {
                            if ((func.IsAbstract && !classIsAbstract) || func.Owner is Interface)
                                report.Add(new ErrorEntry(this.classDef) { Info = func.Name, Type = ErrorType.FunctionImplementationMissing });
                        }
                    }
                }
            }

            //foreach (string key in this.ScopeElements.Keys)
            //{
            //    object obj = this.ScopeElements[key];
            //    // TODO
            //    this.flatScope[key] = obj;
            //}

            return this.flatScope;
        }

        /// <summary>
        /// Check that there are no loops in the inheritance hierarchy and remove duplicates
        /// </summary>
        /// <param name="location"></param>
        /// <param name="report"></param>
        protected virtual bool CheckInheritanceForLoops(ErrorReport report)
        {
            // Detected a loop?
            if (this.marker)
            {
                this.ReportInheritanceLoop(this.classDef, report);
                return false;
            }

            this.marker = true;
            foreach (AbstractType iface in this.BaseTypes)
            {
                if (!iface.CheckInheritanceForLoops(report))
                    return false;
            }
            this.marker = false;

            // Avoid that the following code is executed twice
            if (!this.membersAreParsed)
            {
                this.membersAreParsed = true;

                // By default everything inherits from object
                if (this.BaseTypes.Count == 0 && !(this is ObjectType))
                    this.BaseTypes.Add(ObjectType.Self);

                // Now that all base classes have been parsed, we can parse this class
                this.ParseClassMembers(report);

                // Check for duplicate names, correct overrides etc.
                this.ComputeFlatScope(report);
            }

            return true;
        }

        public override void ParseInheritanceAndMembers(ErrorReport report)
        {
            this.CheckInheritanceForLoops(report);

            base.ParseInheritanceAndMembers(report);
        }

        protected void ReportInheritanceLoop(Node location, ErrorReport report)
        {
            report.Add(new ErrorEntry(location) { Type = ErrorType.InheritanceLoop, Info = this.Name });
        }

        public virtual bool Inherits(AbstractType type)
        {
            if (type == this)
                return true;
            if (type is AbstractType)
            {
                foreach (AbstractType iface in this.BaseTypes)
                {
                    if (iface.Inherits(type))
                        return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Parse the class/interface definition including the inheritance hierarchy and the list of base types
        /// </summary>
        /// <param name="classDef"></param>
        /// <param name="report"></param>
        /// <param name="use"></param>
        public virtual void ParseDef(Node classDef, ErrorReport report, Using use)
        {
            this.classDef = classDef;
            Token token = Helper.GetToken(classDef, SafeScriptConstants.IDENTIFIER);
            this.Name = token.Image;

            // Get a list of all template parameters
            Node templParas = Helper.GetNode(classDef, SafeScriptConstants.TEMPLATE_PARAMETERS);
            if (templParas != null)
            {
                templParas = Helper.GetNode(templParas, SafeScriptConstants.TEMPLATE_PARAMETER_LIST);
                while (templParas != null)
                {
                    string paraName = Helper.GetToken(templParas, SafeScriptConstants.IDENTIFIER).Image;
                    this.TemplateParameters.Add(new TemplateParameterType() { Name = paraName, ParentScope = this, classDef = templParas });
                    templParas = Helper.GetNode(templParas, SafeScriptConstants.TEMPLATE_PARAMETER_LIST);
                }
            }

            // Name mangling. Encode the number of template parameters in the name
            if (this.TemplateParameters.Count > 0)
                this.Name = this.Name + "#" + this.TemplateParameters.Count.ToString();

            // Add the template parameters to the visible scope such that base class types can use them
            var use2 = use;
            // This is only required if there are template parameters
            if (this.TemplateParameters.Count > 0)
            {
                use2 = new Using() { ParentUsing = use };
 
                Dictionary<string, object> tps = new Dictionary<string, object>();
                foreach (TemplateParameterType tpt in this.TemplateParameters)
                {
                    tps[tpt.Name] = tpt;
                }
                // Add the template parameter types to the using
                use2.Stack.Push(tps);
            }

            // Get a list of all base types
            Node baseclasses = Helper.GetNode(classDef, SafeScriptConstants.TYPE_DEF_LIST);
            while (baseclasses != null)
            {
                Node baseclass = Helper.GetNode(baseclasses, SafeScriptConstants.TYPE_DEF);
                this.unresolvedBaseTypes.Add(new TypeResolver()
                {
                    Use = use2,
                    Context = this.ParentScope as Namespace,
                    TypeDefNode = baseclass,
                });
                baseclasses = Helper.GetNode(baseclasses, SafeScriptConstants.TYPE_DEF_LIST);
            }

            // Parse the body of the class
            Node body = Helper.GetNode(classDef, SafeScriptConstants.MAIN);
            if (body != null)
                this.ParseMain(body, report, use);
        }

        public override void ResolveMain(ErrorReport report)
        {
            base.ResolveMain(report);

            // Resolve all base types
            int baseClassCount = 0;
            for (int i = 0; i < this.unresolvedBaseTypes.Count; ++i)
            {
                TypeResolver unresolved = this.unresolvedBaseTypes[i];
                ITypeDef scope = unresolved.ResolveType(report);
                // The name could be resolved?
                if (scope != null)
                {
                    // The name must refere to a type, not a namespace for example
                    AbstractType b = scope as AbstractType;
                    if (b == null)
                    {
                        report.Add(new ErrorEntry(unresolved.TypeDefNode)
                        {
                            Type = ErrorType.NotAType
                        });
                    }
                    else
                    {
                        // The base type is a class?
                        if (b is Class)
                        {
                            // Inheriting from a sealed class is not allowed
                            if ((b as Class).IsSealed)
                            {
                                report.Add(new ErrorEntry(this.classDef) { Type = ErrorType.ClassIsSealed, Info = (b as Class).Name });
                            }
                            baseClassCount++;
                            // Classes may inherit from at most one other class
                            if (this is Class)
                            {
                                if (baseClassCount == 2)
                                {
                                    report.Add(new ErrorEntry(this.classDef) { Type = ErrorType.MultiplClassInheritance, Info = this.Name });
                                }
                            }
                            // Interfaces must not inherit from classes
                            else if (baseClassCount == 1)
                            {
                                report.Add(new ErrorEntry(this.classDef) { Type = ErrorType.ClassInheritanceInInterface, Info = this.Name });
                            }
                        }
                        this.BaseTypes.Add(b);
                    }
                }
            }
        }
    }

    public class TemplateParameterType : AbstractType
    {
        public TemplateParameterType()
        {
            // Each template type is at least an object
            this.BaseTypes.Add(ObjectType.Self);
        }

        public override ITypeDef Instantiate(Dictionary<TemplateParameterType, ITypeDef> templateArguments)
        {
            if (templateArguments.ContainsKey(this))
                return templateArguments[this];
            return this;
        }
    }

    public interface IMember
    {
        MemberAttribute[] Attributes { get; set; }
        T GetAttribute<T>() where T : MemberAttribute;
    }
}
